package cc.blynk.client.core;
import cc.blynk.client.CommandParser;
import cc.blynk.server.core.protocol.enums.Command;
import cc.blynk.server.core.protocol.model.messages.MessageBase;
import cc.blynk.utils.SHA256Util;
import cc.blynk.utils.ServerProperties;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.channels.UnresolvedAddressException;
import java.util.Collections;
import java.util.Random;
import static cc.blynk.server.core.protocol.model.messages.MessageFactory.produce;
/**
* The Blynk Project.
* Created by Dmitriy Dumanskiy.
* Created on 1/31/2015.
*/
public abstract class BaseClient {
protected static final Logger log = LogManager.getLogger(BaseClient.class);
protected final ServerProperties props;
protected final String host;
protected final int port;
protected final Random random;
protected Channel channel;
protected NioEventLoopGroup nioEventLoopGroup;
public BaseClient(String host, int port, Random messageIdGenerator) {
this(host, port, messageIdGenerator, new NioEventLoopGroup(1));
}
public BaseClient(String host, int port, Random messageIdGenerator, ServerProperties serverProperties) {
this.host = host;
this.port = port;
this.random = messageIdGenerator;
this.props = serverProperties;
this.nioEventLoopGroup = new NioEventLoopGroup(1);
}
public BaseClient(String host, int port, Random messageIdGenerator, NioEventLoopGroup nioEventLoopGroup) {
this.host = host;
this.port = port;
this.random = messageIdGenerator;
this.props = new ServerProperties(Collections.emptyMap());
this.nioEventLoopGroup = nioEventLoopGroup;
}
public static MessageBase produceMessageBaseOnUserInput(String line, int msgId) {
String[] input = line.split(" ", 2);
short command;
try {
command = CommandParser.parseCommand(input[0]);
} catch (IllegalArgumentException e) {
log.error("Command not supported {}", input[0]);
return null;
}
String body = input.length == 1 ? "" : input[1];
if (command == Command.REGISTER || command == Command.LOGIN) {
String[] userPass = body.split(" ", 3);
if (userPass.length > 1) {
String email = userPass[0];
String pass = userPass[1];
body = email + "\0" + SHA256Util.makeHash(pass, email) + (userPass.length == 3 ? "\0" + userPass[2].replaceAll(" ", "\0") : "");
}
}
if (command == Command.SHARE_LOGIN || command == Command.GET_GRAPH_DATA) {
body = body.replaceAll(" ", "\0");
}
if (command == Command.HARDWARE || command == Command.BRIDGE || command == Command.EMAIL ||
command == Command.SHARING || command == Command.EXPORT_GRAPH_DATA || command == Command.SET_WIDGET_PROPERTY
|| command == Command.HARDWARE_SYNC) {
body = body.replaceAll(" ", "\0");
}
return produce(msgId, command, body);
}
public void start(BufferedReader commandInputStream) {
this.nioEventLoopGroup = new NioEventLoopGroup(1);
try {
Bootstrap b = new Bootstrap();
b.group(nioEventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(getChannelInitializer());
// Start the connection attempt.
this.channel = b.connect(host, port).sync().channel();
readUserInput(commandInputStream);
} catch (UnresolvedAddressException uae) {
log.error("Host name '{}' is invalid. Please make sure it is correct name.", host);
} catch (ConnectTimeoutException cte) {
log.error("Timeout exceeded when connecting to '{}:{}'. Please make sure host available and port is open on target host.", host, port);
} catch (IOException | InterruptedException e) {
log.error("Error running client. Shutting down.", e);
} catch (Exception e) {
log.error(e);
} finally {
// The connection is closed automatically on shutdown.
nioEventLoopGroup.shutdownGracefully();
}
}
public void start() {
Bootstrap b = new Bootstrap();
b.group(nioEventLoopGroup).channel(NioSocketChannel.class).handler(getChannelInitializer());
try {
// Start the connection attempt.
this.channel = b.connect(host, port).sync().channel();
} catch (InterruptedException e) {
log.error(e);
}
}
protected abstract ChannelInitializer<SocketChannel> getChannelInitializer();
private void readUserInput(BufferedReader commandInputStream) throws IOException {
String line;
while ((line = commandInputStream.readLine()) != null) {
// If user typed the 'quit' command, wait until the server closes the connection.
if ("quit".equals(line.toLowerCase())) {
log.info("Got 'quit' command. Closing client.");
channel.close();
break;
}
MessageBase msg = produceMessageBaseOnUserInput(line, (short) random.nextInt(Short.MAX_VALUE));
if (msg == null) {
continue;
}
send(msg);
}
}
public void send(Object msg) {
channel.writeAndFlush(msg);
}
public boolean isClosed() {
return !channel.isOpen();
}
public ChannelFuture stop() {
ChannelFuture channelFuture = channel.close().awaitUninterruptibly();
nioEventLoopGroup.shutdownGracefully();
return channelFuture;
}
}